// Copyright (c) 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef QUICHE_BLIND_SIGN_AUTH_CACHED_BLIND_SIGN_AUTH_H_
#define QUICHE_BLIND_SIGN_AUTH_CACHED_BLIND_SIGN_AUTH_H_

#include <string>
#include <vector>

#include "absl/base/thread_annotations.h"
#include "absl/status/statusor.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/span.h"
#include "quiche/blind_sign_auth/blind_sign_auth_interface.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/quiche_circular_deque.h"

namespace quiche {

inline constexpr int kBlindSignAuthRequestMaxTokens = 1024;

// CachedBlindSignAuth caches signed tokens generated by BlindSignAuth.
// This class does not guarantee that tokens returned are fresh.
// Tokens may be stale if the backend has rotated its signing key since tokens
// were generated.
// This class is thread-safe.
class QUICHE_EXPORT CachedBlindSignAuth : public BlindSignAuthInterface {
 public:
  CachedBlindSignAuth(
      BlindSignAuthInterface* blind_sign_auth,
      int max_tokens_per_request = kBlindSignAuthRequestMaxTokens)
      : blind_sign_auth_(blind_sign_auth),
        max_tokens_per_request_(max_tokens_per_request) {}

  // Returns signed unblinded tokens and expiration time in a callback.
  // Tokens are single-use. They will not be usable after the expiration time.
  //
  // The GetTokens callback may be called synchronously on the calling thread,
  // or asynchronously on BlindSignAuth's BlindSignMessageInterface thread.
  // The GetTokens callback must not acquire any locks that the calling thread
  // owns, otherwise the callback will deadlock.
  void GetTokens(std::optional<std::string> oauth_token, int num_tokens,
                 ProxyLayer proxy_layer, BlindSignAuthServiceType service_type,
                 SignedTokenCallback callback) override;

  // Removes all tokens in the cache.
  void ClearCache() {
    absl::WriterMutexLock lock(&mutex_);
    cached_tokens_.clear();
  }

  // Returns an attestation challenge in a callback.
  // GetAttestationTokens callbacks will run on the same thread as the
  // BlindSignMessageInterface callbacks.
  // Callers can make multiple concurrent requests to GetTokens.
  // ProxyLayer must be either ProxyB or TerminalLayer, NOT ProxyA.
  // AttestationDataCallback should call AttestAndSign with a separate callback
  // in order to complete the token issuance protocol.
  void GetAttestationTokens(int num_tokens, ProxyLayer layer,
                            AttestationDataCallback callback,
                            SignedTokenCallback token_callback) override;

 private:
  void HandleGetTokensResponse(
      SignedTokenCallback callback, int num_tokens,
      absl::StatusOr<absl::Span<BlindSignToken>> tokens);
  std::vector<BlindSignToken> CreateOutputTokens(int num_tokens)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  void RemoveExpiredTokens() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);

  BlindSignAuthInterface* blind_sign_auth_;
  int max_tokens_per_request_;
  absl::Mutex mutex_;
  QuicheCircularDeque<BlindSignToken> cached_tokens_ ABSL_GUARDED_BY(mutex_);
};

}  // namespace quiche

#endif  // QUICHE_BLIND_SIGN_AUTH_CACHED_BLIND_SIGN_AUTH_H_
